﻿using System;
using System.Collections.Generic;
using Cysharp.Threading.Tasks;
using Internal.KuiHuaBaoDian.Services.Asset;
using KuiHuaBaoDian.Extensions;
using KuiHuaBaoDian.Services.Asset;
using UIOC.Attributes;
using UnityEngine;
using UnityEngine.AddressableAssets;
using UnityEngine.ResourceManagement.AsyncOperations;

namespace KuiHuaBaoDian.Services.Asset {

    /// <summary>
    /// 加载进度
    /// </summary>
    public interface IAssetLoadProgress {

        /// <summary>
        /// 设置进度
        /// </summary>
        /// <param name="value">进度</param>
        void SetProgress(float value);
    }

    public interface IAssetService : IService {

        UniTask<AssetHandle<T>> Load<T>(IAssetRequest request, System.Action<AssetHandle<T>> onComplete = null) where T : UnityEngine.Object;

        UniTask<GameObject> Instantiate(IAssetRequest request, Transform parent = null, bool isActive = true, System.Action<GameObject> onComplete = null);

        /// <summary>
        /// 释放资产
        /// </summary>
        /// <param name="handle">资产句柄</param>
        void Release(AssetHandle handle);

        /// <summary>
        /// 释放直接通过<see cref="Instantiate(IAssetRequest, Transform, bool, Action{GameObject}, Action{GameObject})"/>实例化的<see cref="GameObject"/>对象
        /// </summary>
        /// <param name="gameObject"></param>
        void Release(GameObject gameObject);
    }

    [Implement(typeof(IAssetService))]
    public partial class AssetService : Service, IAssetService, IAssetServiceInternal {

        private sealed class Record {

            public Record(string key, AsyncOperationHandle asyncOperationHandle) {
                Key = key;
                AsyncOperationHandle = asyncOperationHandle;
            }

            public string Key { get; }

            public AsyncOperationHandle AsyncOperationHandle { get; }
        }

        public AssetService() {
            var gameObject = new GameObject(nameof(IAssetService));
            UnityEngine.Object.DontDestroyOnLoad(gameObject);
            gameObject.hideFlags = HideFlags.HideAndDontSave;
            m_InstantiatingTransform = gameObject.transform;
        }

        private readonly Transform m_InstantiatingTransform;

        private readonly Dictionary<AssetHandle, Record> m_LoadedDictionary = new();
        private readonly Dictionary<GameObject, AsyncOperationHandle<GameObject>> m_InstantiatedDictionary = new();

        public async UniTask<GameObject> Instantiate(IAssetRequest request, Transform parent = null, bool isActive = true, Action<GameObject> onComplete = null) {
            UnityEngine.Assertions.Assert.IsNotNull(request);

            if (request.CancellationToken.IsCancellationRequested) {
                onComplete?.Invoke(null);
                return null;
            }

            if (!Validate(request.Key, out var key)) {
                onComplete?.Invoke(null);
                return null;
            }

            var hasParent = parent != null;
            var originParent = parent;
            parent = m_InstantiatingTransform;

            var opHandle = Addressables.InstantiateAsync(key, parent, false, false);
            var operateResult = await OperateHandle(request, opHandle);
            switch (operateResult) {
                case OperateResult.Succeed:
                    if (hasParent && originParent == null) {
                        // failed 因为需要的父节点可能已经被销毁
                        Addressables.Release(opHandle);
                        UnityEngine.Debug.LogWarning($"<color=#FF0032>加载成功，但父节点可能已被销毁</color>，资产名称 [ <color=#00DEFF>{request.Key}</color> ]");
                        onComplete?.Invoke(null);
                        return null;
                    } else {
                        var result = opHandle.Result;
                        m_InstantiatedDictionary[result] = opHandle;
                        result.SetActive(isActive);
                        result.transform.SetParent(originParent);
                        onComplete?.Invoke(result);
                        return result;
                    }
                case OperateResult.Failed:
                    onComplete?.Invoke(null);
                    return null;
                case OperateResult.Cancelled:
                    onComplete?.Invoke(null);
                    return null;
                default:
                    throw new NotSupportedException();
            }
        }

        public async UniTask<AssetHandle<T>> Load<T>(IAssetRequest request, System.Action<AssetHandle<T>> onComplete = null) where T : UnityEngine.Object {
            UnityEngine.Assertions.Assert.IsNotNull(request);

            if (request.CancellationToken.IsCancellationRequested) {
                onComplete?.Invoke(AssetHandle.None);
                return AssetHandle.None;
            }

            if (!Validate(request.Key, out var key)) {
                onComplete?.Invoke(AssetHandle.None);
                return AssetHandle.None;
            }

            var opHandle = Addressables.LoadAssetAsync<T>(key);
            var operateResult = await OperateHandle(request, opHandle);
            switch (operateResult) {
                case OperateResult.Succeed:
                    var handle = new AssetHandle<T> { GUID = System.Guid.NewGuid() };
                    var record = new Record(key, opHandle);
                    m_LoadedDictionary.Add(handle, record);
                    onComplete?.Invoke(handle);
                    return handle;
                case OperateResult.Failed:
                    onComplete?.Invoke(AssetHandle.None);
                    return AssetHandle.None;
                case OperateResult.Cancelled:
                    onComplete?.Invoke(AssetHandle.None);
                    return AssetHandle.None;
                default:
                    throw new System.NotSupportedException();
            }
        }

        private enum OperateResult { Succeed, Failed, Cancelled }
        private async UniTask<OperateResult> OperateHandle(IAssetRequest request, AsyncOperationHandle opHandle) {
            if (request.CancellationToken.IsCancellationRequested) {
                return OperateResult.Cancelled;
            }
            request.LoadProgress?.SetProgress(0);
            await UIOC.Application.Runtime.WaitUntil(() => {
                var condition = opHandle.IsDone || request.CancellationToken.IsCancellationRequested;
                request.LoadProgress?.SetProgress(condition ? 1f : opHandle.PercentComplete);
                return condition;
            });
            if (request.CancellationToken.IsCancellationRequested) {
                Addressables.Release(opHandle);
                return OperateResult.Cancelled;
            } else if (opHandle.Status != AsyncOperationStatus.Succeeded) {
                Addressables.Release(opHandle);
                LogLoadWarning(request);
                return OperateResult.Failed;
            } else if (opHandle.Result == null) {
                Addressables.Release(opHandle);
                return OperateResult.Cancelled;
            }
            request.LoadProgress?.SetProgress(1);
            return OperateResult.Succeed;
        }

        public void Release(AssetHandle handle) {
            UnityEngine.Assertions.Assert.IsTrue(handle.IsNotNone());
            if (m_LoadedDictionary.TryGetValue(handle, out var record)) {
                m_LoadedDictionary.Remove(handle);
                Addressables.Release(record.AsyncOperationHandle);
            }
        }

        public void Release(GameObject gameObject) {
            UnityEngine.Assertions.Assert.IsNotNull(gameObject);
            if (m_InstantiatedDictionary.TryGetValue(gameObject, out var opHandle)) {
                m_InstantiatedDictionary.Remove(gameObject);
                if (!opHandle.IsValid()) {
                    UnityEngine.Debug.LogWarning($"释放资产时，状态非法 {nameof(gameObject)}={gameObject.name}");
                }
                Addressables.Release(opHandle);
            }
        }

        private bool Validate(string key, out string result) {
#if UNITY_EDITOR
            result = key.Trim();
            if (result.Length != key.Length) {
                UnityEngine.Debug.LogError($"[{nameof(IAssetService)}.{nameof(Validate)}()] 警告！资产加载时发现传入的 {nameof(key)} 中首或尾存在空格");
            }
            if (string.IsNullOrEmpty(result)) {
                UnityEngine.Debug.LogWarning($"[{nameof(IAssetService)}.{nameof(Validate)}()] 警告！请求空资产键");
                return false;
            }
#else
            result = key;
#endif
            return true;
        }

        T IAssetServiceInternal.GetValue<T>(AssetHandle<T> handle) {
            UnityEngine.Assertions.Assert.IsTrue(handle != AssetHandle.None);

            if (m_LoadedDictionary.TryGetValue(handle, out var record)) {
                return record.AsyncOperationHandle.Convert<T>().Result;
            } else {
                throw new System.InvalidProgramException();
            }
        }

        private static void LogLoadWarning(IAssetRequest request) {
            if (!request.IsIgnoreFailureLog) {
                UnityEngine.Debug.LogWarning($"<color=#FF0032>加载失败</color>，资产名称 [ <color=#00DEFF>{request.Key}</color> ]");
            }
        }
    }
}

namespace Internal.KuiHuaBaoDian.Services.Asset {

    internal interface IAssetServiceInternal : IAssetService {

        T GetValue<T>(AssetHandle<T> handle) where T : UnityEngine.Object;
    }
}
